package cz.cvut.fit.bugtrac.datacollector;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import cz.cvut.fit.bugtrac.publicmodel.dto.graph.EdgeDTO;
import cz.cvut.fit.bugtrac.publicmodel.dto.graph.GraphDTO;

/**
 * Tool for collecting data from bug pages.
 * 
 * @author czebrpet
 * 
 */
@Component
public class DataCollectorImpl implements DataCollector {

	private static final int HISTORY_DAYS = -14;
	private static final int PARTIAL = 37;

	private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

	protected static transient Logger log = Logger
			.getLogger(DataCollectorImpl.class.getName());

	private RestTemplate rest;

	private static final String OVERVIEW_URL_BEGIN = "https://bugs.eclipse.org/bugs/buglist.cgi?chfieldto=Now;query_format=advanced;chfieldfrom=";
	private static final String OVERVIEW_URL_END = ";order=Importance;bug_status=ASSIGNED&bug_status=REOPENED&ctype=csv";
	private static final String BUG_BASE_URL = "https://bugs.eclipse.org/bugs/show_bug.cgi";

	/**
	 * Tag names
	 */
	private static final String REPORTER = "reporter";
	private static final String ASSIGNER = "assigned_to";
	private static final String BUG_ID = "bug_id";

	/**
	 * 
	 * @param bugIds
	 * @return
	 */
	private MultiValueMap<String, String> prepareFormData(List<String> bugIds) {
		MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
		map.add("ctype", "xml");
		for (String id : bugIds) {
			map.add("id", id);
		}
		map.add("excludefield", "attachmentdata");
		return map;
	}

	/**
	 * @param rest
	 *            the rest to set
	 */
	@Autowired
	public void setRest(RestTemplate rest) {
		this.rest = rest;
	}

	/**
	 * Method for parsing DOM from source string.
	 * 
	 * @param xmlSource
	 * @return
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 * @throws IOException
	 */
	private static Document stringToDom(final String xmlSource)
			throws SAXException, ParserConfigurationException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		InputStream is = new ByteArrayInputStream(
				xmlSource.getBytes(DEFAULT_CHARSET));
		return builder.parse(is);
	}

	/**
	 * Collects the data from bug track pages and returns DTO.
	 * 
	 * @return
	 */
	@Override
	public GraphDTO collectsData() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_MONTH, HISTORY_DAYS);
		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
		String url = OVERVIEW_URL_BEGIN + df.format(cal.getTime())
				+ OVERVIEW_URL_END;
		String bugListResult = rest.getForObject(url, String.class);
		String[] bugList = bugListResult.split("\n");
		List<String> bugIds = new ArrayList<String>();

		for (int i = 1; i < bugList.length; i++) {
			if (!bugList[i].contains("inbox")
					&& !bugList[i].contains("triaged")) {
				bugIds.add(bugList[i].split(",")[0]);
			}
		}
		log.info("Number of bugs after filter: " + bugIds.size());

		int partBugs = bugIds.size() / (PARTIAL - 1);
		int restBugs = bugIds.size() % (PARTIAL - 1);
		int counter = 0;

		Set<EdgeDTO> edges = new HashSet<EdgeDTO>();
		Set<String> nodes = new HashSet<String>();

		// cycle through the whole list with bug ids and return data about bugs
		for (int i = 0; i < PARTIAL; i++) {
			log.info("Parsing " + (i + 1) + "/" + PARTIAL);
			List<String> partBugIds = new ArrayList<String>();
			if (i == PARTIAL - 1) {
				for (int x = 0; x < restBugs; x++) {
					partBugIds.add(bugIds.get(counter));
					counter++;
				}
			} else {
				for (int x = 0; x < partBugs; x++) {
					partBugIds.add(bugIds.get(counter));
					counter++;
				}
			}

			MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
			map = prepareFormData(partBugIds);

			log.info("Post for object - bug ids: " + partBugIds);
			String result = rest.postForObject(BUG_BASE_URL, map, String.class);

			// System.out.println(result);

			Document doc = null;
			try {
				doc = stringToDom(result);
			} catch (Exception e) {
				e.printStackTrace();
			}

			Element root = doc.getDocumentElement();

			NodeList bugs = root.getChildNodes();

			for (int y = 0; y < bugs.getLength(); y++) {
				Node n = bugs.item(y);
				Element bug;

				if (n.getNodeType() == Node.ELEMENT_NODE) {
					bug = (Element) n;

					NodeList ids = bug.getElementsByTagName(BUG_ID);
					NodeList reporters = bug.getElementsByTagName(REPORTER);
					NodeList assigners = bug.getElementsByTagName(ASSIGNER);

					String rep = reporters.item(0).getTextContent().trim();
					String ass = assigners.item(0).getTextContent().trim();
					String id = ids.item(0).getTextContent();

					EdgeDTO edge = new EdgeDTO();
					edge.setReporter(rep);
					edge.setAssigner(ass);
					edge.setName(id);

					if (!rep.equalsIgnoreCase(ass)) {

						if (!nodes.contains(rep)) {
							nodes.add(rep);
						}

						if (!nodes.contains(ass)) {
							nodes.add(ass);
						}

						if (!edges.contains(edge)) {
							edges.add(edge);
						}

					}

					log.fine("Bug ID: " + id + ", reporter: " + rep
							+ ", assigner: " + ass);
				} else {
					log.fine("WARNING: Problem with reading bug. Bug is " + y
							+ ". in the list.");
				}
			}
		}

		log.info("Data collection over - Nodes: " + nodes.size() + " edges: "
				+ edges.size());
		GraphDTO graph = new GraphDTO();
		graph.setEdges(edges);
		graph.setSize(nodes.size());
		graph.setEdgeSize(edges.size());
		graph.setCreated(new Date());

		return graph;
	}

	/**
	 * just for testing
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		DataCollectorImpl dc = new DataCollectorImpl();
		dc.setRest(new RestTemplate());
		dc.collectsData();
	}
}
